home *** CD-ROM | disk | FTP | other *** search
/ Mac-Source 1994 July / Mac-Source_July_1994.iso / Other Langs / abc / examples / advent / article < prev    next >
Encoding:
Text File  |  1994-04-01  |  13.0 KB  |  458 lines  |  [TEXT/R*ch]

  1. An Adventure Program
  2.  
  3. Steven Pemberton
  4. CWI, Amsterdam
  5.  
  6. Copyright (c) Steven Pemberton, CWI, Amsterdam, 1991
  7.  
  8. Adventure style games are very popular in computing circles, and I'm
  9. going to develop a small one here.  Because of space I will have to
  10. leave out many of the advanced features of most adventure games, but
  11. it will give you an idea of how it looks in ABC.  And of course it
  12. will be obvious how the bells and whistles can quickly be added on.
  13.  
  14. As I'm sure you know, a (textual) adventure program works by
  15. describing a scene.  You then give instructions on where to go, or
  16. what to do, and it responds by telling you what happened as a result.
  17. For instance, if it says
  18.  
  19.     You are standing by a building at the end of a road.
  20.     A spring flows from the building.
  21.  
  22. and you reply
  23.  
  24.     > enter building
  25.  
  26. it might reply
  27.  
  28.     You are inside a building, a well house for a spring.
  29.     There is a bottle here.
  30.     There are some keys here.
  31.  
  32. after which the dialogue might proceed as follows:
  33.  
  34.     > take keys
  35.     > leave the building
  36.     Please use 1 or 2 word sentences.
  37.     > leave
  38.     You are outside the building.
  39.     > go west
  40.     You are standing by a stream.
  41.     > go south
  42.     You are at a small slit that the stream runs down.
  43.     A dry river bed carries on ahead.
  44.     > go down
  45.     You don't seem to be able to go that way.
  46.     > south
  47.     You have found a metal grate fixed into the ground.
  48.     > down
  49.     Sorry, you can't do that.
  50.     > open grate
  51.     The grate is open.
  52.     You are at a hole in the ground.
  53.     There is a metal grate lying on the ground.
  54.     > down
  55.     You are in a dim chamber.
  56.     A hole in the ceiling shows the sky above.
  57.  
  58. and so on.
  59.  
  60. The main program making up this adventure looks like this:
  61.  
  62.     HOW TO ADVENTURE:
  63.        START
  64.        GET command
  65.        WHILE command <> "quit":
  66.           OBEY command
  67.           GET command
  68.        FINISH
  69.  
  70. START will initialise some variables, like the place where the player
  71. is, and what the player is holding.  FINISH will print out the score
  72. and so on.  GET will print the prompt, read a line, strip off spaces,
  73. and reduce it to lower-case:
  74.  
  75.     HOW TO GET command:
  76.        GET LINE
  77.        WHILE command = "": GET LINE
  78.     GET LINE:
  79.        WRITE "> "
  80.        READ command RAW
  81.        PUT lower stripped command IN command
  82.  
  83. OBEY has to split a command into its constituent words, and then
  84. decide what action needs to be taken for that command:
  85.  
  86.     HOW TO OBEY command:
  87.        SPLIT command INTO verb AND object
  88.        SELECT:
  89.           verb = "": PASS
  90.           special command: TRY TO MOVE command
  91.           verb = "move": TRY TO MOVE object
  92.           verb = "take": TRY TO TAKE object
  93.           verb = "drop": TRY TO DROP object
  94.           verb = "kill": TRY TO KILL object
  95.           verb = "what": INVENTORY
  96.           ELSE: CAN'T DO verb, object
  97.  
  98. SPLIT does what its name suggests: splits the command into its
  99. constituent words, and makes sure it only consists of one or two
  100. words:
  101.  
  102.     HOW TO SPLIT command INTO verb AND object:
  103.        PUT split command IN words
  104.        SELECT:
  105.           #words = 1: PUT words item 1, "" IN verb, object
  106.           #words = 2: PUT words item 1, words item 2 IN verb, object
  107.           ELSE:
  108.          WRITE "Please use 1 or 2 word sentences." /
  109.          PUT "", "" IN verb, object
  110.  
  111. A nice feature is to allow synonyms for commands, to allow ``go west''
  112. and ``proceed west'' and ``move west'' all to mean the same thing.  We
  113. can do that by having a table of synonyms:
  114.  
  115.     >>> WRITE synonyms["move"]
  116.     {"go"; "proceed"}
  117.  
  118. and then adding in SPLIT:
  119.  
  120.     SHARE synonyms
  121.     ...
  122.     IF SOME word IN keys synonyms HAS verb in synonyms[word]:
  123.        PUT word IN verb
  124.  
  125.  
  126. Moving places
  127.  
  128. In this adventure, each place has a name, which is a short description
  129. you get each time you visit it after the first time (``You are x'',
  130. such as ``outside the building'' above).  Then, each place has a long
  131. description used for describing it the first time you go there.  Such
  132. a description is stored as a table of lines, for instance
  133.  
  134.     >>> WRITE description["inside large hall"]
  135.     {[1]: "This is a large hall."; [2]: "There is an exit to the west."}
  136.  
  137. To display such a message neatly, we can define the following command:
  138.  
  139.     HOW TO DISPLAY message:
  140.        FOR line IN message:
  141.           WRITE line /
  142.  
  143.  
  144.     >>> DISPLAY description["inside large hall"]
  145.     This is a large hall.
  146.     There is an exit to the west.
  147.  
  148. Then there is a map of all locations, which gives for each location a
  149. table of directions that the player can go in, and where that
  150. direction leads to.
  151.  
  152.     >>> WRITE map["inside the building"]
  153.     {["out"]: "outside the building"}
  154.     >>> WRITE map["outside the building"]
  155.     {["in"]: "inside the building"; ["south"]: "standing by the stream";
  156.     ["west"]: "in the forest"}
  157.  
  158. We can play a nasty trick on the player:
  159.  
  160.     >>> WRITE map["in the forest"]
  161.     {["east"]: "in the forest"; ["north"]: "in the forest";
  162.     ["south"]: "in the forest"; ["west"]: "standing by the stream"}
  163.  
  164. Moving is attempted by means of the command TRY TO MOVE.  All commands
  165. beginning TRY TO first check that the conditions for the action are
  166. acceptable, and only then do the action.  The current location is held
  167. in place: TRY TO MOVE checks that the direction asked for is in the
  168. map for the current place:
  169.  
  170.     HOW TO TRY TO MOVE direction:
  171.        SHARE map, place
  172.        SELECT:
  173.           direction = "":
  174.          WRITE "Where to?" /
  175.           direction in keys map[place]:
  176.          MOVE TO map[place][direction]
  177.           ELSE:
  178.          WRITE "You don't seem to be able to go that way" /
  179.  
  180. MOVE TO does the actual moving.  For now here is a simple version, but
  181. it will get more involved later.
  182.  
  183.     HOW TO MOVE TO there:
  184.        SHARE place
  185.        PUT there IN place
  186.        DESCRIBE place
  187.  
  188. (DESCRIBE describes a place and the objects to be found there; you'll
  189. see it shortly.)
  190.  
  191. In OBEY, you will have noticed the lines
  192.  
  193.        SELECT:
  194.           special command: TRY TO MOVE command
  195.  
  196. This is to allow the player to say south instead of go south, by
  197. seeing if the command is already in the map for the current place:
  198.  
  199.     HOW TO REPORT special command:
  200.        SHARE map, place
  201.        REPORT command in keys map[place]
  202.  
  203. Notice that it also allows you to use commands instead of directions
  204. in the map.  For instance, when at the grate, you can open the grate
  205. by having two places, an open grate and a closed grate:
  206.  
  207.     >>> WRITE map["at closed grate"]
  208.     {["north"]: "at slit"; ["open grate"]: "at open grate"}
  209.  
  210.  
  211. Taking and dropping objects
  212.  
  213. Different objects are left lying about at various places.  These are
  214. recorded in a table objects.  Just as with places, each object has a
  215. simple name, to be used when the player wants to know what is being
  216. carried, and a longer description when an object is first found.
  217.  
  218.     >>> WRITE objects["inside the building"]
  219.     {"bottle"; "keys"}
  220.     >>> WRITE description["keys"]
  221.     There are some keys here.
  222.  
  223. Now I can show you DESCRIBE.  It remembers which places have already
  224. been described (and therefore visited), and only gives the long
  225. description the first time:
  226.  
  227.     HOW TO DESCRIBE place:
  228.        SHARE description, objects, visited
  229.        SELECT:
  230.           place in visited:
  231.          WRITE "You are ", place /
  232.           ELSE:
  233.          DISPLAY description[place]
  234.          INSERT place IN visited
  235.        FOR object IN objects for place:
  236.           DISPLAY description[object]
  237.  
  238. Notice here the line ``FOR object IN objects for place:''.  Not every
  239. place may be recorded in the objects table, so it is a shorthand to
  240. save repeated checks to see if it is:
  241.  
  242.     HOW TO RETURN property for thing:
  243.        SELECT:
  244.           thing in keys property: RETURN property[thing]
  245.           ELSE: RETURN {}
  246.  
  247. You'll find it used again later on.
  248.  
  249. Then there is a list of what the player is carrying, called holding,
  250. which is initially empty.  To find out what is being carried, the
  251. player can ask for an inventory:
  252.  
  253.     HOW TO INVENTORY:
  254.        SHARE holding
  255.        SELECT:
  256.           holding = {}:
  257.          WRITE "You aren't carrying anything" /
  258.           ELSE:
  259.          WRITE "You are carrying: "
  260.          LIST holding
  261.  
  262. This uses a useful command to neatly print a list of objects:
  263.  
  264.     HOW TO LIST things:
  265.        PUT "" IN separator
  266.        FOR object IN things:
  267.           WRITE separator, object
  268.           PUT ", " IN separator
  269.        WRITE /
  270.  
  271.  
  272.     >>> LIST objects["inside the building"]
  273.     bottle, keys
  274.  
  275. Another useful tool is a test to see if an object is currently being
  276. carried:
  277.  
  278.     HOW TO REPORT carrying object:
  279.        SHARE holding
  280.        REPORT object in holding
  281.  
  282. and another to test if an object is present:
  283.  
  284.     HOW TO REPORT present object:
  285.        SHARE objects, place
  286.        REPORT object in objects for place
  287.  
  288. TRY TO TAKE can now check that the object is present, that it's not
  289. already being carried and so on, before actually taking it:
  290.  
  291.     HOW TO TRY TO TAKE object:
  292.        SHARE holding
  293.        SELECT:
  294.           object = "":
  295.          WRITE "Which object?" /
  296.           carrying object:
  297.          WRITE "You're already carrying it!" /
  298.           NOT present object:
  299.          WRITE "I see no `object`." /
  300.           #holding > 6:
  301.          WRITE "You can't carry any more." /
  302.           ELSE:
  303.          TAKE object
  304.  
  305. TAKE looks like this, again a simple version for now:
  306.  
  307.     HOW TO TAKE object:
  308.        SHARE holding, objects, place
  309.        REMOVE object FROM objects[place]
  310.        INSERT object IN holding
  311.  
  312.  
  313. TRY TO DROP is similar:
  314.  
  315.     HOW TO TRY TO DROP object:
  316.        SELECT:
  317.           object = "": WRITE "Which object?" /
  318.           NOT carrying object: WRITE "You're not holding it!" /
  319.           ELSE: DROP object
  320.  
  321.  
  322.     HOW TO DROP object:
  323.        SHARE holding, objects, place
  324.        REMOVE object FROM holding
  325.        INCLUDE object IN objects FOR place
  326.  
  327. The command INCLUDE adds an item to a table:
  328.  
  329.     HOW TO INCLUDE object IN property FOR thing:
  330.        IF thing not.in keys property:
  331.           PUT {} IN property[thing]
  332.        INSERT object IN property[thing]
  333.  
  334.  
  335. Conditions and side effects
  336.  
  337. One of the tricks of adventure games is that certain actions are not
  338. possible unless you are at a certain place, or you are carrying a
  339. certain thing, and some actions have unexpected side-effects.
  340.  
  341. For instance, you shouldn't be able to open the grate if you aren't
  342. carrying the keys.  So we can alter MOVE TO to check for this:
  343.  
  344.     HOW TO MOVE TO there:
  345.        SHARE place
  346.        SELECT:
  347.           opening.grate AND NOT carrying "keys":
  348.          WRITE "I don't seem able to open the grate" /
  349.           ELSE:
  350.          PUT there IN place
  351.          DESCRIBE place
  352.     opening.grate:
  353.        REPORT (place, there) = ("at closed grate", "at open grate")
  354.  
  355. Similarly, somewhere in the cave there is a bird, but you can only
  356. catch it if you're carrying the cage.  Furthermore, the jangling of
  357. the keys frightens it.  So we can alter TAKE to do this:
  358.  
  359.     HOW TO TAKE object:
  360.        SHARE holding, objects, place
  361.        SELECT:
  362.           object = "bird" AND carrying "keys":
  363.          WRITE "The bird flutters off in fright." /
  364.           object = "bird" AND NOT carrying "cage":
  365.          WRITE "You don't seem able to catch the bird." /
  366.           ELSE:
  367.          REMOVE object FROM objects[place]
  368.          INSERT object IN holding
  369.  
  370. An example of a side-effect is that dropping the bird is the only way
  371. to scare off the snake (should you meet it):
  372.  
  373.     HOW TO DROP object:
  374.        SHARE holding, objects, place
  375.        IF object = "bird" AND present "snake":
  376.           WRITE "With a great flurry the bird attacks the snake." /
  377.           WRITE "The snake flees into the darkness." /
  378.           REMOVE "snake" FROM objects[place]
  379.        REMOVE object FROM holding
  380.        INCLUDE object IN objects FOR place
  381.  
  382. (Obviously, TAKE should also be changed to prevent you from trying to
  383. take the snake.)
  384.  
  385. Removing objects with extreme prejudice
  386.  
  387. Now you've seen that there are living creatures in the cave.  Certain
  388. of them are undesirable to the player's well-being and score, and in
  389. the brutal tradition of adventure games must be eliminated.  Of course
  390. some are harmless, but computers only do what they are told...
  391.  
  392.     HOW TO TRY TO KILL object:
  393.        SELECT:
  394.           object = "":
  395.          WRITE "Which object?" /
  396.           (NOT present object) AND (NOT carrying object):
  397.          WRITE "I see no `object`" /
  398.           ELSE:
  399.          KILL object
  400.  
  401.  
  402.     HOW TO KILL object:
  403.        SHARE holding, objects, place
  404.        SELECT:
  405.           object = "bird":
  406.          WRITE "How cruel! The poor bird dies with a mournful peep." /
  407.          ELIMINATE
  408.          INCLUDE "dead bird" IN objects FOR place
  409.           object = "snake":
  410.          WRITE "Attacking the snake is both dangerous and ineffective." /
  411.           ELSE:  It's not a living creature
  412.          WRITE "It's already dead!" /
  413.     ELIMINATE:
  414.        SELECT:
  415.           carrying object: REMOVE object FROM holding
  416.           present object: REMOVE object FROM objects[place]
  417.  
  418.  
  419. Odds and ends
  420.  
  421. Well, that's the body of the adventure.  Of course, lots of extra
  422. places, objects, beings and commands must be added, but that's just a
  423. case of more of the same.
  424.  
  425. In OBEY, if it can't obey your command, it invokes CAN'T DO.  As a
  426. nicety this prints funny remarks for certain commands.  For instance,
  427. if you're at the stream, you might try ``swim'':
  428.  
  429.     >>> DISPLAY funny["swim"]
  430.     The water would get into my circuits.
  431.  
  432.  
  433.     HOW TO CAN'T DO verb:
  434.        SHARE funny
  435.        SELECT:
  436.           verb in keys funny: DISPLAY funny[verb]
  437.           ELSE:
  438.          WRITE "Sorry, you can't do that" /
  439.  
  440. As a final touch, you might want to add the commands ``save'' and
  441. ``restore'' to OBEY, so you can save a game, and come back later to it
  442. (or so you can try something, and if it fails restore it and try
  443. something else).
  444.  
  445. This is remarkably easy.  Since the state of the game is reflected by
  446. a small number of variables, you can just put them in another
  447. variable:
  448.  
  449.     HOW TO SAVE:
  450.        SHARE saved, holding, objects, place
  451.        PUT holding, objects, place IN saved
  452.  
  453.  
  454.     HOW TO RESTORE:
  455.        SHARE saved, holding, objects, place
  456.        PUT saved IN holding, objects, place
  457.        DESCRIBE place
  458.